--***********************************************************
--**                LEMMY/ROBERT JOHNSON                   **
--***********************************************************

AdjacentFreeTileFinder = {}

-- returns true if test is not blocked from src by a wall.
AdjacentFreeTileFinder.privTrySquareForWalls = function(src, test)

    if src == nil or test == nil then return false; end

    if src:getX() < test:getX() and src:getY() == test:getY() then
        if test:Is("DoorWallW") and not test:isDoorBlockedTo(src) then return true; end
        if test:Is(IsoFlagType.cutW) or test:Is(IsoFlagType.collideW) then return false; end
    end
    if src:getX() > test:getX() and src:getY() == test:getY()  then
        if src:Is("DoorWallW") and not src:isDoorBlockedTo(test) then return true; end
        if src:Is(IsoFlagType.cutW)  or src:Is(IsoFlagType.collideW) then return false; end
    end

    if src:getY() < test:getY() and src:getX() == test:getX()  then
        if test:Is("DoorWallN") and not test:isDoorBlockedTo(src) then return true; end
        if test:Is(IsoFlagType.cutN)  or test:Is(IsoFlagType.collideN)  then return false; end
    end
    if src:getY() > test:getY() and src:getX() == test:getX()  then
        if src:Is("DoorWallN") and not src:isDoorBlockedTo(test) then return true; end
        if src:Is(IsoFlagType.cutN)  or src:Is(IsoFlagType.collideN) then return false; end
    end

    if src:getX() ~= test:getX() and src:getY() ~= test:getY() then
        if not AdjacentFreeTileFinder.privTrySquareForWalls2(src, test:getX(), src:getY(), src:getZ()) or
            not AdjacentFreeTileFinder.privTrySquareForWalls2(src, src:getX(), test:getY(), src:getZ()) or
            not AdjacentFreeTileFinder.privTrySquareForWalls2(test, test:getX(), src:getY(), src:getZ()) or
            not AdjacentFreeTileFinder.privTrySquareForWalls2(test, src:getX(), test:getY(), src:getZ()) then
            return false
        end
    end

    return true;
end

AdjacentFreeTileFinder.privTrySquareForWalls2 = function(src, x, y, z)
    return AdjacentFreeTileFinder.privTrySquareForWalls(src, getCell():getGridSquare(x, y, z))
end

-- returns true if test is adjacent to src.
AdjacentFreeTileFinder.privTrySquare = function(src, test)

     -- if either is null, its not adjacent.
     if(src == nil or test == nil) then return false; end

     -- if either are on a different floor, not adjacent.
     if src:getZ() ~= test:getZ() then return false; end

     -- if there is a wall between the two tiles, not adjacent.
     if not AdjacentFreeTileFinder.privTrySquareForWalls(src, test) then
         return false;
    end

     -- if the test one is solid / not walkable, not adjacent.
    -- if there is no floor on test, not adjacent.
    if not AdjacentFreeTileFinder.privCanStand(test) then
       return false;
    end



     -- adjacent!
    return true;

end

-- returns true if b is either the same tile as a, or is adjacent to a.
AdjacentFreeTileFinder.isTileOrAdjacent8 = function(a, b)

--print("Testing is tile or adjacent");

    if a == b then
        --print("Same tile.")
        return true;
    end

    if(math.abs(a:getX() - b:getX()) > 1) then
        --print("Further than 1 away")
        return false;
    end
    if(math.abs(a:getY() - b:getY()) > 1) then
        --print("Further than 1 away")
        return false;
    end

    local res = AdjacentFreeTileFinder.privTrySquare(a, b);
    if res == true then
        --print("Is adjacent")
    else
        --print("Is not adjacent")
    end
    return res;
end
-- returns true if b is either the same tile as a, or is adjacent to a.
AdjacentFreeTileFinder.isTileOrAdjacent = function(a, b)

--print("Testing is tile or adjacent");

    if a == b then
        --print("Same tile.")
        return true;
    end

    if(math.abs(a:getX() - b:getX()) > 1) then
        --print("Further than 1 away")
        return false;
    end
    if(math.abs(a:getY() - b:getY()) > 1) then
        --print("Further than 1 away")
        return false;
    end

    local res = AdjacentFreeTileFinder.privTrySquare(a, b);
    if res == true then
        --print("Is adjacent")
    else
        --print("Is not adjacent")
    end
    return res;
end

-- find a free tile that's adjacent to gridSquare and return it.

AdjacentFreeTileFinder.Find = function(gridSquare, playerObj)
    local choices = {}
    local choicescount = 1;
    -- first try straight lines (N/S/E/W)
    local a = gridSquare:getAdjacentSquare(IsoDirections.W)
    local b = gridSquare:getAdjacentSquare(IsoDirections.E)
    local c = gridSquare:getAdjacentSquare(IsoDirections.N)
    local d = gridSquare:getAdjacentSquare(IsoDirections.S)

    -- for each of them, test that square then if it's 'adjacent' then add it to the table for picking.
    if AdjacentFreeTileFinder.privTrySquare(gridSquare, a) then table.insert(choices, a); choicescount = choicescount + 1; end
    if AdjacentFreeTileFinder.privTrySquare(gridSquare, b) then table.insert(choices,  b); choicescount = choicescount + 1;end
    if AdjacentFreeTileFinder.privTrySquare(gridSquare, c) then  table.insert(choices, c); choicescount = choicescount + 1;end
    if AdjacentFreeTileFinder.privTrySquare(gridSquare, d) then table.insert(choices, d); choicescount = choicescount + 1; end

    -- only do diags if no other choices.
    if choicescount == 1 then
        -- now do diags.
        a = gridSquare:getAdjacentSquare(IsoDirections.NW)
        b = gridSquare:getAdjacentSquare(IsoDirections.NE)
        c = gridSquare:getAdjacentSquare(IsoDirections.SW)
        d = gridSquare:getAdjacentSquare(IsoDirections.SE)

        if AdjacentFreeTileFinder.privTrySquare(gridSquare, a) then  table.insert(choices, a); choicescount = choicescount + 1; end
        if AdjacentFreeTileFinder.privTrySquare(gridSquare, b) then  table.insert(choices,  b); choicescount = choicescount + 1;end
        if AdjacentFreeTileFinder.privTrySquare(gridSquare, c) then  table.insert(choices, c); choicescount = choicescount + 1;end
        if AdjacentFreeTileFinder.privTrySquare(gridSquare, d) then  table.insert(choices, d); choicescount = choicescount + 1; end

    end

    -- if we have multiple choices, pick the one closest to the player
    if choicescount > 1 then
       local lowestdist = 100000;
       local distchoice = nil;

       for i, k in ipairs(choices) do
          local dist = k:DistToProper(playerObj);
          if dist < lowestdist then
              lowestdist = dist;
              distchoice = k;
          end
       end

        return distchoice;
    end
    return nil;
end

local function tryAddChoice(square, dir, choices)
    local adjacent = square:getAdjacentSquare(dir)
    if adjacent and AdjacentFreeTileFinder.privTrySquare(square, adjacent) then
        table.insert(choices, adjacent)
    end
end

AdjacentFreeTileFinder.FindClosest = function(gridSquare, playerObj)
    local choices = {}
    tryAddChoice(gridSquare, IsoDirections.NW, choices)
    tryAddChoice(gridSquare, IsoDirections.N, choices)
    tryAddChoice(gridSquare, IsoDirections.NE, choices)
    tryAddChoice(gridSquare, IsoDirections.E, choices)
    tryAddChoice(gridSquare, IsoDirections.SE, choices)
    tryAddChoice(gridSquare, IsoDirections.S, choices)
    tryAddChoice(gridSquare, IsoDirections.SW, choices)
    tryAddChoice(gridSquare, IsoDirections.W, choices)
    if #choices == 0 then
        return nil
    end
    local minDist = 100000
    local closestSq = nil
    for _,square in ipairs(choices) do
        local dist = square:DistToProper(playerObj)
        if dist < minDist then
            minDist = dist
            closestSq = square
        end
    end
    return closestSq
end

local function getClosestChoice(choices, playerObj)
    local minDist = 100000
    local closest = nil
    for _,square in ipairs(choices) do
        local dist = square:DistTo(playerObj)
        if dist < minDist then
            minDist = dist
            closest = square
        end
    end
    return closest
end

AdjacentFreeTileFinder.FindWindowOrDoor = function(gridSquare, window, playerObj)
	local choices = {};

    if AdjacentFreeTileFinder.privGetNorth(gridSquare, window) then
        local n = gridSquare:getAdjacentSquare(IsoDirections.N);
        if instanceof(window, "IsoCurtain") and window:getType() == IsoObjectType.curtainS then n = nil end
        if n and playerObj:getCurrentSquare():getRoom() == n:getRoom() and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, n) then
            table.insert(choices, n)
        end
        if playerObj:getCurrentSquare():getRoom() == gridSquare:getRoom() and AdjacentFreeTileFinder.privCanStand(gridSquare) then
            table.insert(choices, gridSquare)
        end
        if #choices == 0 then
            if n and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, n) then
                table.insert(choices, n)
            end
            if AdjacentFreeTileFinder.privCanStand(gridSquare) then
                table.insert(choices, gridSquare)
            end
        end
    else
        local w = gridSquare:getAdjacentSquare(IsoDirections.W);
        if instanceof(window, "IsoCurtain") and window:getType() == IsoObjectType.curtainE then w = nil end
        if w and playerObj:getCurrentSquare():getRoom() == w:getRoom() and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, w) then
            table.insert(choices, w)
        end
        if playerObj:getCurrentSquare():getRoom() == gridSquare:getRoom() and AdjacentFreeTileFinder.privCanStand(gridSquare) then
            table.insert(choices, gridSquare)
        end
        if #choices == 0 then
            if w and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, w) then
                table.insert(choices, w)
            end
            if AdjacentFreeTileFinder.privCanStand(gridSquare) then
                table.insert(choices, gridSquare)
            end
        end
    end

    -- only do diags if no other choices.
    if #choices == 0 then
        -- now do diags.
        local a = gridSquare:getAdjacentSquare(IsoDirections.NW)
        local b = gridSquare:getAdjacentSquare(IsoDirections.NE)
        local c = gridSquare:getAdjacentSquare(IsoDirections.SW)
        local d = gridSquare:getAdjacentSquare(IsoDirections.SE)

        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, a) then table.insert(choices, a) end
        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, b) then table.insert(choices, b) end
        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, c) then table.insert(choices, c) end
        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, d) then table.insert(choices, d) end

    end

    -- if we have multiple choices, pick the one closest to the player
    return getClosestChoice(choices, playerObj)
end

AdjacentFreeTileFinder.FindWall = function(gridSquare, north, playerObj)
    local choices = {};
    
    if north then
        local n = gridSquare:getAdjacentSquare(IsoDirections.N);
        if n and playerObj:getCurrentSquare():getRoom() == n:getRoom() and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, n) then
            table.insert(choices, n)
        end
        if playerObj:getCurrentSquare():getRoom() == gridSquare:getRoom() and AdjacentFreeTileFinder.privCanStand(gridSquare) then
            table.insert(choices, gridSquare)
        end
        if #choices == 0 then
            if n and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, n) then
                table.insert(choices, n)
            end
            if AdjacentFreeTileFinder.privCanStand(gridSquare) then
                table.insert(choices, gridSquare)
            end
        end
    else
        local w = gridSquare:getAdjacentSquare(IsoDirections.W);
        if w and playerObj:getCurrentSquare():getRoom() == w:getRoom() and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, w) then
            table.insert(choices, w)
        end
        if playerObj:getCurrentSquare():getRoom() == gridSquare:getRoom() and AdjacentFreeTileFinder.privCanStand(gridSquare) then
            table.insert(choices, gridSquare)
        end
        if #choices == 0 then
            if w and AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, w) then
                table.insert(choices, w)
            end
            if AdjacentFreeTileFinder.privCanStand(gridSquare) then
                table.insert(choices, gridSquare)
            end
        end
    end
    
    -- only do diags if no other choices.
    if #choices == 0 then
        -- now do diags.
        local a = gridSquare:getAdjacentSquare(IsoDirections.NW)
        local b = gridSquare:getAdjacentSquare(IsoDirections.NE)
        local c = gridSquare:getAdjacentSquare(IsoDirections.SW)
        local d = gridSquare:getAdjacentSquare(IsoDirections.SE)
        
        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, a) then table.insert(choices, a) end
        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, b) then table.insert(choices, b) end
        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, c) then table.insert(choices, c) end
        if AdjacentFreeTileFinder.privTrySquareWindow(gridSquare, d) then table.insert(choices, d) end
    
    end
    
    -- if we have multiple choices, pick the one closest to the player
    return getClosestChoice(choices, playerObj)
end

function AdjacentFreeTileFinder.FindEdge(gridSquare, dir, playerObj, preferSameSquare)
    local choices = {}
    if AdjacentFreeTileFinder.privCanStand(gridSquare) then
        if preferSameSquare then return gridSquare end
        table.insert(choices, gridSquare)
    end
    
    local n = gridSquare:getAdjacentSquare(IsoDirections.N)
    local s = gridSquare:getAdjacentSquare(IsoDirections.S)
    local w = gridSquare:getAdjacentSquare(IsoDirections.W)
    local e = gridSquare:getAdjacentSquare(IsoDirections.E)
    if dir == IsoDirections.N or dir == IsoDirections.S then
        tryAddChoice(gridSquare, IsoDirections.W, choices)
        tryAddChoice(gridSquare, IsoDirections.E, choices)
    end
    if dir == IsoDirections.W or dir == IsoDirections.E then
        tryAddChoice(gridSquare, IsoDirections.N, choices)
        tryAddChoice(gridSquare, IsoDirections.S, choices)
    end
    return getClosestChoice(choices, playerObj)
end

-- returns true if test is adjacent to src.
AdjacentFreeTileFinder.privTrySquareWindow = function(src, test)

    if test == src then return true; end
     -- if either is null, its not adjacent.
     if(src == nil or test == nil) then return false; end

     -- if either are on a different floor, not adjacent.
     if src:getZ() ~= test:getZ() then return false; end

    -- is diag test and src are on diagnel, test straight lines between them
    if test:getX() ~= src:getX() and test:getY() ~= src:getY() then
        local betweenA = getCell():getGridSquare(test:getX(), src:getY(), test:getZ());
        local betweenB = getCell():getGridSquare(src:getX(), test:getY(), test:getZ());

        -- must be no interfering walls on either of the directions
        if(not AdjacentFreeTileFinder.privTrySquareForWalls(src, betweenA) or not AdjacentFreeTileFinder.privTrySquareForWalls(src, betweenB)) then
            return false;
        end
        if(not AdjacentFreeTileFinder.privTrySquareForWalls(test, betweenA) or not AdjacentFreeTileFinder.privTrySquareForWalls(test, betweenB)) then
            return false;
        end
    end

    if not AdjacentFreeTileFinder.privCanStand(test) then
        return false
    end

     -- adjacent!
    return true;

end

AdjacentFreeTileFinder.privCanStand = function(test)
	if not test then return false end

    if test:Is(IsoFlagType.solid) then
        return false;
    end

    -- Player can stand on solidtrans squares adjacent to windows.
    if test:Is(IsoFlagType.solidtrans) then
        local hasWindow = false
        if test:Is(IsoFlagType.windowW) or test:Is(IsoFlagType.windowN) then
            hasWindow = true
        end
        if not hasWindow then
            local s = test:getAdjacentSquare(IsoDirections.S)
            if s and s:Is(IsoFlagType.windowN) then
                hasWindow = true
            end
        end
        if not hasWindow then
            local e = test:getAdjacentSquare(IsoDirections.E)
            if e and e:Is(IsoFlagType.windowW) then
                hasWindow = true
            end
        end
        if not hasWindow then
            return false
        end
    end

    if not test:TreatAsSolidFloor() then
       return false;
    end

    return true
end

AdjacentFreeTileFinder.privGetNorth = function(gridSquare, object)
    if object and object.getNorth then
        return object:getNorth()
    end
    return gridSquare and (gridSquare:Is(IsoFlagType.cutN) or gridSquare:Is(IsoFlagType.collideN))
end
